/*
 *  attribute_info.c
 *
 *  Copyright (C) 2011  Jason<linux8192@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <assert.h>
#include <stdio.h>
#include "debug.h"
#include "type.h"
#include "mem.h"
#include "attribute_info.h"
#include "constant_info.h"


/*
 * Attribute info getters
 *
 * return the attribute info length in class file (NOT sizeof(struct) !!!)
 */

static int parse_code_attribute(attr_t **attr, const char *buff, const cp_info_t **cp_list)
{
	int		i;
	char		*buff_cur = buff;
	code_attr_t	*attr_code;

	LOG_TRACE();

	attr_code = *attr = mem_alloc(sizeof(code_attr_t));
	assert(attr_code);

	__parse_attribute_base(attr_code->base, buff_cur);

	attr_code->max_stack = read_u2_and_move_cur(&buff_cur);
	attr_code->max_locals = read_u2_and_move_cur(&buff_cur);
	attr_code->code_length = read_u4_and_move_cur(&buff_cur);

	attr_code->code = mem_alloc(attr_code->code_length);
	assert(attr_code->code_length == 0 || attr_code->code);
	memcpy(attr_code->code, buff_cur, attr_code->code_length);
	buff_cur += attr_code->code_length;

	attr_code->exception_table_length = read_u2_and_move_cur(&buff_cur);

	attr_code->exception_table = mem_alloc(attr_code->exception_table_length * sizeof(exception_table_t));
	assert(attr_code->exception_table_length == 0 || attr_code->exception_table);

	for (i = 0; i < attr_code->exception_table_length; ++i) {
		attr_code->exception_table[i].start_pc = read_u2_and_move_cur(&buff_cur);
		attr_code->exception_table[i].end_pc = read_u2_and_move_cur(&buff_cur);
		attr_code->exception_table[i].handler_pc = read_u2_and_move_cur(&buff_cur);
		attr_code->exception_table[i].catch_type = read_u2_and_move_cur(&buff_cur);
	}

	attr_code->attributes_count = read_u2_and_move_cur(&buff_cur);

	attr_code->attributes = mem_alloc(sizeof(attr_t *) * attr_code->attributes_count);
	assert(attr_code->attributes_count == 0 || attr_code->attributes);
	buff_cur += parse_attribute_pool(buff, attr_code->attributes, attr_code->attributes_count, cp_list);

	return buff_cur - buff;
}

static int release_code_attribute(code_attr_t *code_attr)
{
	int i;

	/* Code buffer */
	mem_free(code_attr->code);

	/* Exception table buffer */
	mem_free(code_attr->exception_table);

	/* Attribute buffer */
	release_attribute_pool(code_attr->attributes, code_attr->attributes_count);

	/* Self */
	mem_free(code_attr);

	return 0;
}

static int parse_constant_value_attribute(attr_t **attr, const char *buff, const cp_info_t **cp_list)
{
	char			*buff_cur = buff;
	constant_value_attr_t	*attr_cv;

	LOG_TRACE();

	attr_cv = *attr = mem_alloc(sizeof(constant_value_attr_t));
	assert(attr_cv);

	__parse_attribute_base(attr_cv->base, buff_cur);

	attr_cv->constant_value_index = read_u2_and_move_cur(&buff_cur);

	return buff_cur - buff;
}

static int parse_deprecated_attribute(attr_t **attr, const char *buff, const cp_info_t **cp_list)
{
	char			*buff_cur = buff;
	deprecated_attr_t	*attr_deprecated;

	LOG_TRACE();

	attr_deprecated = *attr = mem_alloc(sizeof(deprecated_attr_t));
	assert(attr_deprecated);

	__parse_attribute_base(attr_deprecated->base, buff_cur);

	return buff_cur - buff;
}

static int parse_exception_attribute(attr_t **attr, const char *buff, const cp_info_t **cp_list)
{
	char			*buff_cur = buff;
	exception_attr_t	*attr_exception;
	int			exceptions_size = 2 * read_u2(buff + 6);
	int			i;

	LOG_TRACE();

	attr_exception = *attr = mem_alloc(sizeof(exception_attr_t) + exceptions_size);
	assert(attr_exception);

	__parse_attribute_base(attr_exception->base, buff_cur);

	attr_exception->number_of_exceptions = read_u2_and_move_cur(&buff_cur);

	for (i = 0; i < attr_exception->number_of_exceptions; ++i) {
		attr_exception->exception_index_table[i] = read_u2_and_move_cur(&buff_cur);
	}

	return buff_cur - buff;
}

static int parse_inner_classes_attribute(attr_t **attr, const char *buff, const cp_info_t **cp_list)
{
	char			*buff_cur = buff;
	inner_classes_attr_t	*attr_icls;
	int			inner_cls_info_size = INNER_CLASS_INFO_SIZE * read_u2(buff + 6);
	int			i;

	LOG_TRACE();

	attr_icls = *attr = mem_alloc(sizeof(inner_classes_attr_t) + inner_cls_info_size);
	assert(attr_icls);

	__parse_attribute_base(attr_icls->base, buff_cur);

	attr_icls->number_of_classes = read_u2_and_move_cur(&buff_cur);

	for (i = 0; i < attr_icls->number_of_classes; ++i) {
		attr_icls->classes[i].inner_class_info_index = read_u2_and_move_cur(&buff_cur);
		attr_icls->classes[i].outer_class_info_index = read_u2_and_move_cur(&buff_cur);
		attr_icls->classes[i].inner_name_index = read_u2_and_move_cur(&buff_cur);
		attr_icls->classes[i].inner_class_access_flags = read_u2_and_move_cur(&buff_cur);
	}

	return buff_cur - buff;
}

static int parse_line_number_table_attribute(attr_t **attr, const char *buff, const cp_info_t **cp_list)
{
	char	*buff_cur = buff;
	int	line_no_info_size = LINE_NUMBER_INFO_SIZE * read_u2(buff + 6);
	int	i;
	line_number_table_attr_t	*attr_lno;

	LOG_TRACE();

	attr_lno = *attr = mem_alloc(sizeof(line_number_table_attr_t) + line_no_info_size);
	assert(attr_lno);

	__parse_attribute_base(attr_lno->base, buff_cur);

	attr_lno->line_number_table_length = read_u2_and_move_cur(&buff_cur);

	for (i = 0; i < attr_lno->line_number_table_length; ++i) {
		attr_lno->line_number_table[i].start_pc = read_u2_and_move_cur(&buff_cur);
		attr_lno->line_number_table[i].line_number = read_u2_and_move_cur(&buff_cur);
	}

	return buff_cur - buff;
}

static int parse_local_variable_table_attribute(attr_t **attr, const char *buff, const cp_info_t **cp_list)
{
	char	*buff_cur = buff;
	int	local_var_table_size = LOCAL_VARIABLE_INFO_SIZE * read_u2(buff + 6);
	int	i;
	local_variable_table_attr_t	*attr_lvt;

	LOG_TRACE();

	attr_lvt = *attr = mem_alloc(sizeof(local_variable_table_attr_t) + local_var_table_size);
	assert(attr_lvt);

	__parse_attribute_base(attr_lvt->base, buff_cur);

	attr_lvt->local_variable_table_length = read_u2_and_move_cur(&buff_cur);

	for (i = 0; i < attr_lvt->local_variable_table_length; ++i) {
		attr_lvt->local_variable_table[i].start_pc = read_u2_and_move_cur(&buff_cur);
		attr_lvt->local_variable_table[i].length = read_u2_and_move_cur(&buff_cur);
		attr_lvt->local_variable_table[i].name_index = read_u2_and_move_cur(&buff_cur);
		attr_lvt->local_variable_table[i].descriptor_index = read_u2_and_move_cur(&buff_cur);
		attr_lvt->local_variable_table[i].index = read_u2_and_move_cur(&buff_cur);
	}

	return buff_cur - buff;
}

static int parse_source_file_attribute(attr_t **attr, const char *buff, const cp_info_t **cp_list)
{
	char			*buff_cur = buff;
	source_file_attr_t	*attr_sf;

	LOG_TRACE();

	attr_sf = *attr = mem_alloc(sizeof(source_file_attr_t));
	assert(attr_sf);

	__parse_attribute_base(attr_sf->base, buff_cur);

	attr_sf->source_file_index = read_u2_and_move_cur(&buff_cur);

	return buff_cur - buff;
}

static int parse_synthetic_attribute(attr_t **attr, const char *buff, const cp_info_t **cp_list)
{
	char			*buff_cur = buff;
	synthetic_attr_t	*attr_syth;
	LOG_TRACE();

	attr_syth = *attr = mem_alloc(sizeof(synthetic_attr_t));
	assert(*attr);

	__parse_attribute_base(attr_syth->base, buff_cur);

	return buff_cur - buff;
}


int release_attribute_pool(attr_t **list, const int count)
{
	int	i;

	assert(list);

	for (i = 0; i < count; ++i) {
		assert(list[i]);
		mem_free(list[i]);
	}

	mem_free(list);

	return 0;
}

typedef int (*ATTRIBUTE_PARSER_FUNC)(attr_t **, const char *, const cp_info_t **);

static ATTRIBUTE_PARSER_FUNC get_attribute_parser_by_id(int tag_id)
{
	switch (tag_id) {
	case ATTRIBUTE_CODE:			return parse_code_attribute;
	case ATTRIBUTE_CONSTANT_VALUE:		return parse_constant_value_attribute;
	case ATTRIBUTE_DEPRECATED:		return parse_deprecated_attribute;
	case ATTRIBUTE_EXCEPTION:		return parse_exception_attribute;
	case ATTRIBUTE_INNER_CLASSES:		return parse_inner_classes_attribute;
	case ATTRIBUTE_LINE_NUMBER_TABLE:	return parse_line_number_table_attribute;
	case ATTRIBUTE_LOCAL_VARIABLE_TABLE:	return parse_local_variable_table_attribute;
	case ATTRIBUTE_SOURCE_FILE:		return parse_source_file_attribute;
	case ATTRIBUTE_SYNTHETIC:		return parse_synthetic_attribute;
	default:
		LOGE("%s:%d: Unknown attribute tag id %d\n",
		     __FUNCTION__, __LINE__, tag_id);
	};

	return NULL;
}

static int get_attribute_id_by_name(const char *attr_name)
{
	int	i;

	/*
	 * Corresponding name strings which reference to JVM spec
	 * Notice that they are ordered by related macro definations
	 */
	const char *g_attribute_names[] = {
		"Code",
		"ConstantValue",
		"Deprecated",
		"Exceptions",
		"InnerClasses",
		"LineNumberTable",
		"LocalVariableTable",
		"SourceFile",
		"Synthetic"
	};

	int max = sizeof(g_attribute_names) / sizeof(g_attribute_names[0]);

	assert(attr_name);

	for (i = 0; i < max; ++i) {
		if (!strcmp(g_attribute_names[i], attr_name)) {
			return i;
		}
	}

	LOGE("%s:%d, unkown attribute name %s\n",
	     __FUNCTION__, __LINE__, attr_name);

	return -1;
}

int parse_attribute_pool(const char *buff, attr_t **attr_list, const int count, const cp_info_t **cp_list)
{
	int	i;
	int	tag_id;
	int	attr_name_index;
	int	attr_length;
	char	*attr_name;
	char	*cur = buff;
	ATTRIBUTE_PARSER_FUNC	attr_parser;

	cp_field_ref_info_t	*cp_field_ref_info;

	LOG_ENTER();

	for (i = 0; i < count; ++i) {
		attr_name_index = read_u2(cur);
		LOGV("attr_name_index = %d\n", attr_name_index);
		attr_name = get_utf8_string_by_cp_index(cp_list, attr_name_index);
		tag_id = get_attribute_id_by_name(attr_name);
		LOGV("attr_tag_id = %d\n", tag_id);
		attr_parser = get_attribute_parser_by_id(tag_id);

		cur += attr_parser(&attr_list[i], cur, cp_list);
	}

	LOG_LEAVE();
	return cur - buff;
}
